在数组里漫游

  高效而优雅地访问数组(以及其他类似数据结构)是许多算法(3.8节、第18章)实现的关键。这种访问可以通过到数组的一个指针再加上一个下标,或者通过一个到数组元素的指针进行。下面是采用下标遍历一个字符串的例子:

    void fi(char v[])
    {
        for(int i = 0; v[i] != 0; i++) use(v[i]);
    }

这等价于下面通过指针遍历:

    void fp(char v[])
    {
        for(char* p = v; *p != 0; p++) use(*p);
    }

前缀运算符 * 对指针做间接,所以 *p 就是p所指的字符;而++对指针做增量操作,使其索引这个数组中的下一个字符。

  没有任何内在的理由说某种方式快于另一种。通过新型的编译器,这两个例子完全可能产生一摸一样的代码(见5.9[8])。程序员可以根据自己的逻辑或者美学观点选择其一。

  将算术运算符+、-、++或--应用于指针的结果依赖于被指对象的类型。当某个算术运算符被应用于类型T*的指针p时,假定p是指向类型T的对象的数组中的一个元素;那么p+1将指向下一个元素,而p-1将指向前一个元素。这也意味着p+1的整数值比p的整数值大sizeof(T)。例如执行

    #include <iostream>
    int main()
    {
        int vi[10];
        short vs[10];

        std::cout << &vi[0] << ' ' << &vi[1] << '\n';
        std::cout << &vs[0] << ' ' << &vs[1] << '\n';
    }

可能产生

0x7fffaef0    0x7fffaef4
0x7fffaedc    0x7fffaede

对指针值,默认方式采用的是16进制记法输出。上面这些是在我所用的实现上的情况,其中sizeof(short)是2而sizeof(int)是4。

  只有在两个指针指向同一个数组的元素时,指针之间相减才有定义(虽然语言并没有一个严格的规则保证这一点)。如果从一个指针减去另一个指针,结果就是这两个指针之间的数组元素个数(一个整数)。在一个指针上加一个整数,或者减一个整数,得到的结果还是一个指针值。如果这个值并不指向原来那个指针所指的数组的元素或者是超出其末端一个位置,那么使用这个值产生的结果是无定义的。例如,

    void f()
    {
        int v1[10];
        int v2[10];

        int i1 = &v1[5] - &v1[3];    // i1 = 2
        int i2 = &v1[5] - &v2[3];    // 结果无定义

        int* p1 = v2 + 2;            // p1 = &v2[2]
        int* p2 = v2 - 2;            // *p2无定义
    }

复杂的指针算术通常并不必要,最好避免使用。指针相加没有任何意义,因此是不允许的。

  数组不具有自描述性,因为并不保证与数组一起保存着这个数组的元素个数。这就意味着,如果要遍历一个数组,而该数组并不像字符串那样包含一个结束符,我们就必须以某种方式提供它的元素个数。例如,

    void fp(char v[], unsigned int size)
    {
        for(int i = 0; i < size; i++) use(v[i]);

        const int N = 7;
        char v2[N];
        for(int i = 0; i < N; i++) use(v2[i]);
    }

请注意,大多数C++实现都不提供对数组范围的检查。这种数组的概念从本质上就是非常低级的,更高级的数组概念可以通过类的方式提供,参见3.7.1节。

🔚